/*! FlippieBook 0.1 copyright 2011 Ron Valstar */
/**
 * FlippieBook 0.1
 * @param {object} a value object
 */
if (!FlippieBook) {
	var FlippieBook = function(o) {
		try { TWEEN; } catch (err) { alert("You need to include Sole's tween.js"); return; }
		try { jQuery; } catch (err) { alert("You need to include jQuery"); return; }
		//
		// init vars
		var bDebug = true;
		//
		var bTouch = false;
		try{document.createEvent('TouchEvent');bTouch=true;}catch(err){}
		//
		var bDown = false;
		//
		var bL2R = false;
		//
		var iHalfW;
		//
		var pStart;
		var pLast;
		//
		var iDownT;
		//
		var aTRBL = ['top','right','bottom','left'];
		//
		var oDefault = {
			id:			'book'		// id of the target node
			,pageNodeType:	'li'	// tag-type the target node contains, these will function as the pages
			,width:		320			// width of the book
			,height:	240			// height of the book
			,page:	0				// start page
			,showPageNumbers: true	// shows the page number in the corner of the page // TODO: implement
			,shadow: .2				// shadow opacity
			,highlight: .2			// highlight opacity
			,z:	100					// the z-index used for page ordering if the target node has the z-index set to 'auto' (otherwise the target node's z-index is used)
			,pageChange: null		// a callback function when a page has changed
			,useCanvas: true		// canvas is uses to create a curved top and bottom, creating more depth
			,round:	16				// amount of pixels added to top and bottom for the canvas curve
			,useHash: true			// the book will react to and alter the location.hash
			,hashPrefix: 'page'		// prefix for location.hash
			,pageShade: false		// enable the idle page highlight and shadow // TODO: implement
			,foldShade: true		// show shades and highlights when folding
		};
		//
		//
		//
		o = $.extend({},oDefault,o);
		iHalfW = o.width/2;
		//
		var aMouseReturn = ['a','video','input','textarea','pre'];
		//
		var bCanvas = o.useCanvas&&!!document.createElement('canvas').getContext;//false;//
		//
		// restore
		$.fn.restore = function(show){
			if (this.parents('.flippieBook').length) {
				if (show===undefined) show = true;
				this.width(iHalfW).find('>div.wrap').css({left:'0px'});
				if (show) this.show();
			}
			return this;
		};
		//
		var iCurrentPage = o.page;
		//
		var oAnim = {i:0};
		//
		var $Body = $('body');
		//
		var $Book;
		var $Pages;
		//
		var $ShadeBL;
		var $ShadeBR;
		var $ShadeWL;
		var $ShadeWR;
		var $ShadeBWL;
		var $ShadeBWR;
		//
		var $PgF;
		var $PgB;
		var $ShF;
		var $HlF;
		var $ShB;
		var $ShL;
		var $ShR;
		//
		var $Canvas;
		var oContext;
		var iOuter = o.round;//64;//16;
		var iCw = o.width;
		var iCh = o.height+2*iOuter;
		var fCurveCenter = .01;
		//
		var $Run;
		//
		// run
		(function run(){
			TWEEN.update();
			requestAnimationFrame(run);
		})();
		//
		// init
		function init() {
			//
			// book holder
			$Book = $('#'+o.id).addClass('flippieBook').mousedown(handleDrag);
			if ($Book.length===0) trace('The node with id \''+o.id+'\' could not be found.');
			//
			var sZ = $Book.css('zIndex');
			if (sZ!='auto') o.z = parseInt(sZ);
			//
			if ($('#flippieBookStyle').length===0) $Body.append('<style id="flippieBookStyle">'
				+'.flippieBook {'
				+'	position:relative;'
				+'	display:block;'
				+'	width:'+o.width+'px;'
				+'	height:'+o.height+'px;'
				+'	padding:0;'
				+'	list-style:none;'
				+'}'
				+'	.flippieBook '+o.pageNodeType+' {'
				+'		position:absolute;'
				+'		display:block;'
				+'		width:'+iHalfW+'px;'
				+'		height:'+o.height+'px;'
				+'		margin:0;'
				+'		padding:0;'
				+'		overflow:hidden;'
				+'	}'
				+'		.flippieBook '+o.pageNodeType+' div.wrap {'
				+'			position:relative;'
				+'			width:'+iHalfW+'px;'
				+'			height:'+o.height+'px;'
				+'		}'
				+'	.flippieBook img.shade {'
				+'		position:absolute;'
				+'		z-index:'+(o.z+1)+';'
				+'		display:block;'
				+'		width:'+iHalfW+'px;'
				+'		height:'+o.height+'px;'
				+'		margin:0;'
				+'		padding:0;'
				+'	}'
				+'	.flippieBook canvas {'
				+'		position:absolute;'
				+'		z-index:'+(o.z-3)+';'
				+'		display:block;'
				+'		top:'+(-iOuter)+'px;'
				+'		margin:0;'
				+'		padding:0;'
				+'	}'
			+'</style>');
			//
			// initial pages
			setPages();
			$Pages.each(function(i,el){initPage($(el));});
			//
			// check location hash
			var aHash = location.hash.split('#');
			if (aHash.length>1) {
				var sHash = aHash[1].replace(o.hashPrefix,'');
				if (isNaN(sHash)) sHash = getPageNr(sHash);
				iCurrentPage = Math.min(Math.max(sHash,0),$Pages.length);
			}
			//
			// location.hash bind
			if (o.useHash) $(window).bind('hashchange',hashChange);
			//
			// shades
			var oShade = {
				 bl:	"iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABJJREFUeNpiYGBg+A8EDAABBgAN+wP9QoT8VAAAAABJRU5ErkJggg=="
				,br:	"iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABJJREFUeNpi+P//PwMQ/AcIMAAV8wP9R/WpRQAAAABJRU5ErkJggg=="
				,wl:	"iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABJJREFUeNpi/A8EDAwMjAABBgAZ+QP/gI03CAAAAABJRU5ErkJggg=="
				,wr:	"iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABJJREFUeNpi/P//PwMQ/AcIMAAV/AP+X9zaoQAAAABJRU5ErkJggg=="
				,bwl:	"iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAYAAAAb4BS0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABRJREFUeNpiZGBg+A8EQIqBESDAAB39A/+OlbIWAAAAAElFTkSuQmCC"
				,bwr:	"iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAYAAAAb4BS0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpi/P//PwMQ/GdkZGQACDAAJf0EAakjuSYAAAAASUVORK5CYII="
			};
			$.each(oShade,function(s,img){
				$Shade = $('<img src="data:image/png;base64,'+img+'" class="shade" />').appendTo($Book).hide();
				$Shade.load(function(e){
					var mShade = e.currentTarget;
					var iSw = mShade.naturalWidth||mShade.width;
					var iSh = mShade.naturalHeight||mShade.height;
					trace('iSw:',iSw,iSh); // TRACE ### iSw
				});
				switch (s) {
					case 'bl': $ShadeBL = $Shade; break;
					case 'br': $ShadeBR = $Shade; break;
					case 'wl': $ShadeWL = $Shade; break;
					case 'wr': $ShadeWR = $Shade; break;
					case 'bwl': $ShadeBWL = $Shade; break;
					case 'bwr': $ShadeBWR = $Shade; break;
				}
			});
			//
			// left and right shade/highlight
			if (o.pageShade) {
				var fShWprt = .6;
				var fShOpct = .1;
				$ShL = $ShadeBR.clone().appendTo($Book).css({
					opacity:fShOpct
					,left: (1-fShWprt)*iHalfW+'px'
					,zIndex: o.z-1
				}).width(fShWprt*iHalfW).show();
				$ShR = $ShadeWL.clone().appendTo($Book).css({
					opacity:fShOpct
					,left: iHalfW+'px'
					,zIndex: o.z-1
				}).width(fShWprt*iHalfW).show();
			}
			//
			// canvas
			if (bCanvas) {
				$Canvas = $('<canvas width="'+iCw+'" height="'+iCh+'" />').appendTo($Book);
				oContext = $Canvas.get(0).getContext('2d');
			}
			//
			// document for drag-up
			var $Document = $(document).mousemove(handleDrag).mouseup(handleDrag);
			if (bTouch) {
				$Book.get(0).ontouchstart = handleDrag;
				$Document.get(0).ontouchmove = handleDrag;
				$Document.get(0).ontouchend = handleDrag;
			}
			//
			// create temp div
			if (false&&bDebug) $Run = $('<pre></pre>').appendTo($Body).css({
				 position: 'fixed'
				,left: '10px'
				,top: '10px'
				,padding: '10px'
				,backgroundColor: 'rgba(255,255,255,0.8)'
				,zIndex: '9999'
				,border: '1px solid gray'
				,fontFamily: 'monospace, Courier'
				,fontSize: '12px'
			});
			//
//			setPage();
//			trace('##################'); // TRACE ### 
//			animFromTo(.0001,.0001,100); // throws error: Uncaught TypeError: Cannot call method 'width' of undefined
//			trace('##################'); // TRACE ### 
			//
			// show initial page
			setPage();
		}
		//
		// initPage
		function initPage(page) {
			page.hide().wrapInner('<div class="wrap"></div>');
			var sBgClr = page.css('background-color');
			var sBgImg = page.css('background-image');
			var sBgPos = page.css('background-position');

			var $Wrap = page.find('>div.wrap').css({
				backgroundColor:		sBgClr
				,backgroundImage:		sBgImg
				,backgroundPosition:	sBgPos
			});
			page.css({background:'transparent'});
			//
			// move li padding to .wrap
			$.each(aTRBL,function(i,s){
				var iLiPad = parseInt(page.css('padding-'+s).replace(/[^0-9]/gi,''));
				var iWrPad = parseInt($Wrap.css('padding-'+s).replace(/[^0-9]/gi,''));
				$Wrap.css('padding-'+s,(iLiPad+iWrPad)+'px');
			});
			page.css('padding',0);
			//
			// set .wrap.width from padding
			var iWmin = 0;
			var iHmin = 0;
			$.each(aTRBL,function(i,s){
				var iPad = parseInt($Wrap.css('padding-'+s).replace(/[^0-9]/gi,''));
				if (i%2) iWmin += iPad;
				else iHmin += iPad;
			});
			$Wrap.width(page.width()-iWmin);
			$Wrap.height(page.height()-iHmin);
			//
			//
			if (sBgImg!='none') {
				var mImg = new Image();
				$(mImg).load(function(e){page.data('img_e',e);});
				mImg.src = sBgImg.replace('url(','').replace(')','');
				page.data('img',mImg);
			}
		}
		//
		// addPage
		function addPage(page,at){
			if (at===undefined) at = $Pages.length-1;
			initPage($(page).insertAfter($($Pages[at])));
			setPages();
			if (iCurrentPage===at||iCurrentPage===at-1) setPage();
		}
		//
		// setPages
		function setPages() {
			$Pages = $Book.find('>'+o.pageNodeType);
			$Pages.filter(':even').addClass('even');
			$Pages.filter(':not(:even)').removeClass('even');
		}
		//
		// getPageNr
		function getPageNr(s) {
			var $Node = $('#'+s);
			return $Node.length>0?$Node.index():iCurrentPage;
		}
		//
		// setPage
		function setPage(nr,l2r) {
			if (isNaN(nr)) nr = getPageNr(nr);
			//
//			if (o.useHash&&iCurrentPage!==nr) {
//				location.hash = o.hashPrefix+nr;
//			}
			if (o.useHash) {
				var sHash = o.hashPrefix+nr;
				if (location.hash!=sHash) location.hash = sHash;
			}
			//
			if (nr!==undefined&&iCurrentPage!==nr) {
				iCurrentPage = nr;
				if (o.pageChange) o.pageChange(nr);
			}
			//
			var iRight = Math.min(2*Math.ceil(iCurrentPage/2),$Pages.length);
			//
			$Pages.hide();
			$Pages.filter(':eq('+iRight+')').restore().css({left:o.width/2,zIndex:l2r?o.z-2:o.z});
			if (iCurrentPage!==0) $Pages.filter(':eq('+(iRight-1)+')').restore().css({left:0,zIndex:l2r?o.z:o.z-2});
			//
			if (l2r!==undefined) {
				if (l2r) {
					$Pages.filter(':eq('+(iRight-2)+')').css({left:0,zIndex:o.z-1}).restore();
					$Pages.filter(':eq('+(iRight-3)+')').css({left:0,zIndex:o.z-2}).restore();
				} else {
					$Pages.filter(':eq('+(iRight+1)+')').css({left:o.width/2,zIndex:o.z-1}).restore();
					$Pages.filter(':eq('+(iRight+2)+')').css({left:o.width/2,zIndex:o.z-2}).restore();
				}
			}
		}
		//
		// handleDrag
		function handleDrag(e){
			bReturn = true;
			var pNow = eventPos(e);
			var iXps = Math.min(Math.max(pNow.x,0),o.width);
			var fPos = iXps/o.width;
			switch (e.type) {
				case 'mousedown': case 'touchstart':
					trace('is:',$(e.target).is(aMouseReturn.join(',')),aMouseReturn); // TRACE ### e
//					if (aMouseReturn.indexOf(e.target.nodeName.toLowerCase())>=0) return true;
					if ($(e.target).is(aMouseReturn.join(','))) return true;
					//
					if (!canFlip(fPos<.5)) return true;
					else bL2R = fPos<.5;
					//
					iDownT = (new Date()).getTime();
					//
					pStart = pNow.clone();
					pLast = pStart.clone();
					bDown = true;
					//
					preparePages();
					//
					animFromTo(bL2R?0:1,fPos,100);
					//
					bReturn = false;
				break;
				case 'mouseup': case 'touchend':
					if (bDown) { // click or drag
						if (pNow&&pStart&&pNow.equals(pStart)&&(new Date()).getTime()-iDownT<250) {
							animFromTo(fPos,fPos<.5?1:0);
						} else {
							if (fPos===0||fPos===1)	endTurn(fPos);
							else					animFromTo(fPos,fPos>.5?1:0);
						}
					}
					bDown = false;
				break;
				case 'mousemove': case 'touchmove':
					if (bDown) {
						stepPage(fPos);
						bReturn = false;
						pLast = pNow;
					}
				break;
			}
			return bReturn;
		}
		//
		// preparePages
		function preparePages(){
			var iRight = Math.min(2*Math.ceil(iCurrentPage/2),$Pages.length);
			//
			$PgF = $Pages.filter(':eq('+(bL2R?iRight-1:iRight)+')');
			$PgB = $Pages.filter(':eq('+(bL2R?iRight-2:iRight+1)+')');
			//
			$ShF = bL2R?$ShadeBL:$ShadeBR;
			$HlF = bL2R?$ShadeBWL:$ShadeBWR;
			$ShB = bL2R?$ShadeBR:$ShadeBL;
			$.each([$ShF,$HlF,$ShB],function(i,o){o.width(iHalfW).css({opacity:0}).show();});
			//
			setPage(iCurrentPage,bL2R);
		}
		//
		// canFlip
		function canFlip(prev){
			if (iCurrentPage<=0&&prev) return false;
			else if (iCurrentPage>=($Pages.length-1)&&!prev) return false;
			return true;
		}
		//
		// prevNext
		function prevNext(prev){
			if (canFlip(prev)) {
				bL2R = prev;
				preparePages();
				animFromTo(bL2R?0:1,bL2R?1:0);
			}
		}
		//
		// animFromTo
		function animFromTo(from,to,t){
			trace('animFromTo:',from,to,t); // TRACE ### animFromTo
			oAnim.i = from;
			var bToEnd = to===0||to===1;
			var oTween = new TWEEN.Tween(oAnim).to({i:to},t||300).easing(bToEnd?TWEEN.Easing.Cubic.EaseOut:TWEEN.Easing.Cubic.EaseIn).onUpdate(function(o){
				stepPage(oAnim.i);
			});
			if (bToEnd) oTween.onComplete(function(){endTurn(to);});
			oTween.start();
		}
		//
		// stepPage
		function stepPage(f){
			var iXps = f*o.width;
			var fHPos = 2*(f-.5);
//			var fMPos = 1-f;
			var fAlph = Math.sin(f*Math.PI);
			var fHAlp = Math.sin(f/2*Math.PI);
			var fFAlp = Math.sin((1-f)/2*Math.PI);
			
			//
			if ($Run) $Run.html(
				 'bL2R:  '+bL2R+"\n"
				+'fPos:  '+f+"\n"
				+'fHPos: '+fHPos+"\n"
				+'fAlph: '+fAlph+"\n"
				+'fHAlp: '+fHAlp+"\n"
			);
			//
			var iWmin = o.width-iXps;
			//
			// front page
			if ((bL2R&&f<.5)||(!bL2R&&f>.5)) {
				var iFw = Math.ceil((bL2R?-1:1)*fHPos*iHalfW);
				$PgF.width(iFw).css({left:Math.floor(bL2R?iXps:iHalfW)+'px'});
				if (bL2R) $PgF.find('>div.wrap').css({left:-iXps+'px'});
				if (!$PgF.is(':visible')) $PgF.show();
			} else {
				if ($PgF&&$PgF.is(':visible')) $PgF.hide().find('>div.wrap').css({left:'0px'});
			}
			// backpage
			var iBw = Math.ceil(bL2R?iXps/2:iWmin/2);
			$PgB.width(iBw).css({left:Math.ceil(bL2R?iXps-iBw:iXps)+'px'});
			if (bL2R) $PgB.find('>div.wrap').css({left:-Math.ceil(iHalfW-iBw)+'px'});
			//
			// shadow/highlight/shadow
			var iShW = Math.ceil(.5*fAlph*iHalfW);
			$ShF.width(iShW).css({left:(bL2R?iXps:iXps-iShW)+'px',opacity:fAlph*o.shadow});
			// highlight
			var fHlA = (bL2R?fFAlp:fHAlp)*o.highlight;
			var iHlX = Math.floor(bL2R?iXps-iBw:iXps);
			var iHlW = Math.ceil(bL2R?iBw:iWmin/2);
			$HlF.width(iHlW).css({left:iHlX+'px',opacity:fHlA});
			// shadow
//			var iSBw = (iHalfW-iWmin/2)/2;
			$ShB.width(iShW).css({left:(bL2R?iXps/2-iShW:iXps+iWmin/2)+'px',opacity:(bL2R?fFAlp:fHAlp)*o.shadow});
			//
			// draw the curved paper onto canvas
			if (bCanvas){
				var $Wrap = $PgB.find('>div.wrap');
				//
				oContext.clearRect(0,0,iCw,iCh);
				var mImg = $PgB.data('img');
				var bImg = !!mImg;

//				trace('$PgB.data(img):',!!oContext.createPattern,$PgB.data('img')); // TRACE ### $PgB.data('img')
				if (bImg) {
					try{
						oContext.fillStyle = oContext.createPattern($PgB.data('img'), 'repeat');
					}catch(err){
						oContext.fillStyle = '#880000';
					}
				} else oContext.fillStyle = $Wrap.css('background-color');
				// top
				oContext.beginPath();
				oContext.moveTo(iXps, iOuter);
				oContext.lineTo(iXps, iOuter-.5*fAlph*iOuter);
				if (bL2R)	oContext.quadraticCurveTo(iHlX+fCurveCenter*(iXps-iHlX),iOuter-fAlph*iOuter, iHlX,iOuter);
				else		oContext.quadraticCurveTo(iXps+(.5-fCurveCenter)*iWmin,iOuter-fAlph*iOuter, iXps+iWmin/2,iOuter);
				// bottom
				oContext.moveTo(iXps, iCh-iOuter);
				oContext.lineTo(iXps, iCh-(iOuter-.5*fAlph*iOuter));
				if (bL2R)	oContext.quadraticCurveTo(iHlX+fCurveCenter*(iXps-iHlX),iCh-(iOuter-fAlph*iOuter), iHlX,iCh-iOuter);
				else		oContext.quadraticCurveTo(iXps+(.5-fCurveCenter)*iWmin,iCh-(iOuter-fAlph*iOuter), iXps+iWmin/2,iCh-iOuter);
				oContext.moveTo(0,0);
				// fill
				oContext.save();
				if (bImg) {
					var aSpos = $Wrap.css('background-position').split(' ');
					var iXo = parseInt(aSpos[0].match(/[0-9]*/)[0]);
					var iYo = parseInt(aSpos[1].match(/[0-9]*/)[0]);
					if (bL2R)	oContext.translate(iXo+iXps-iHalfW,iYo+iOuter);
					else		oContext.translate(iXo+iXps,iYo+iOuter);
				}
				oContext.fill();
				oContext.restore();
				// draw shadow
				oContext.save();
				oContext.globalCompositeOperation = 'source-atop';
				if (bL2R)	oContext.translate(iXps/2,0);
				else		oContext.translate(iXps,0);
				var fScl = bL2R?iXps/6:iWmin/6; // TODO iWmin/(img.width*2)
				oContext.scale(fScl,iCh);
				oContext.globalAlpha = fHlA;
				oContext.drawImage($HlF.get(0),0,0);
				oContext.restore();
			}
		}
		//
		// endTurn
		function endTurn(pos){
			trace('endTurn:',pos); // TRACE ### endTurn
			if ((bL2R?0:1)===pos) setPage();
			else setPage(iCurrentPage+(bL2R?-2:2));
			$.each([$ShadeBL,$ShadeBR,$ShadeWL,$ShadeWR,$ShadeBWL,$ShadeBWR],function(i,o){o.width(iHalfW).css({opacity:0}).hide();});
		}
		//
		// hashChange
		function hashChange(){
			var aHash = location.hash.split('#');
			if (aHash.length>1) {
				var sHash = aHash[1].replace(o.hashPrefix,'');
				if (isNaN(sHash)) sHash = getPageNr(sHash);
				setPage(parseInt(sHash));
				trace('hashChange:',aHash[1],sHash); // TRACE # ## hashChange
			}
		}
		//
		//
		// addInteractiveElement
		function addInteractiveElement(s){
			trace('addInteractiveElement:',s,$.isArray(s)); // TRACE ### addInteractiveElement
			if ($.isArray(s)) $.merge(aMouseReturn,s);
			else aMouseReturn.push(s);
		}
		// removeInteractiveElement
		function removeInteractiveElement(s){
			var a; // TODO: define a
			var iPos = aMouseReturn.indexOf(s);
			if (iPos>=0) a.splice(iPos,1);
		}
		// interactiveElements
		function interactiveElements(){
			return aMouseReturn;
		}
		//
		//
		// eventPos
		function eventPos(e){
			var oTouch;
			if (e.targetTouches) oTouch = e.targetTouches[0];
			return point(
				 (oTouch||e).pageX - findPos($Book[0])
				,(oTouch||e).pageY - findPos($Book[0],false)
			);
		}
		function findPos(obj,bx) {
			if (bx===undefined) bx = true;
			var pos = 0;
			if(obj.offsetParent)
			while(1) {
				pos += bx?obj.offsetLeft:obj.offsetTop;
				if(!obj.offsetParent) break;
				obj = obj.offsetParent;
			} else if(bx?obj.x:obj.y)
			pos += bx?obj.x:obj.y;
			return pos;
		}
		//
		// point
		function point(_x,_y) {
			var o = {
				 x:_x
				,y:_y
				,subtract: function(p) {
					o.x -= p.x;
					o.y -= p.y;
					return o;
				}
				,add: function(p) {
					o.x += p.x;
					o.y += p.y;
					return o;
				}
				,multiply: function(f) {
					o.x *= f;
					o.y *= f;
					return o;
				}
				,divide: function(f) {
					o.x /= f;
					o.y /= f;
					return o;
				}
				,size:			function() { return Math.sqrt(o.x*o.x+o.y*o.y); }
				,normalize:		function() { return o.divide(o.size()); }
				,normalized:	function() { return o.clone().normalize(); }
				,radians:		function() { return Math.atan2(o.y,o.x); }
				,angle:			function() { return o.radians()*180; }
				,clone:			function() { return point(o.x,o.y); }
				,equals:		function(p) { return o.x===p.x&&o.y===p.y; }
				,toString:		function() { return '[object point '+o.x+', '+o.y+']'; }
			};
			return o;
		}
		//
		// trace
		function trace(s){
			if (!bDebug) return;
			try{window.console.log.apply(console,arguments);}catch(err){}
		}
		//
		// return
		init();
		return {
			 pages:		function(){return $Pages.length;}
			,getPage:	function(){return iCurrentPage;}
			,setPage:	setPage
			,setPages:	setPages
			,addPage:	addPage
			,previous:	function(){prevNext(true);}
			,prev:		function(){prevNext(true);}
			,next:		function(){prevNext(false);}
			,first:		function(){setPage(0);}
			,last:		function(){setPage($Pages.length-1);}

			,addInteractiveElement:		addInteractiveElement
			,removeInteractiveElement:	removeInteractiveElement
			,interactiveElements:		interactiveElements

			,toString: function() {
				return "[object FlippieBook "+o.id+"]";
			}
		};
	};
}


if ( !window.requestAnimationFrame ) {
	window.requestAnimationFrame = ( function() {
		return window.webkitRequestAnimationFrame ||
		window.mozRequestAnimationFrame ||
		window.oRequestAnimationFrame ||
		window.msRequestAnimationFrame ||
			function(callback) {
				window.setTimeout(callback,25);
		};
	} )();
}
